﻿using UnityEngine;
using System;
using UnityEngine.Networking;
using System.IO;
using System.Text;
using System.Collections.Generic;


namespace xres
{
    //todo: 出错时数据reset

    public class ExtractInternalFiles : BaseAsyncAction
    {
        const int Max_Concurrent_Copy_Num = 5;

        public const string Err_Load_External_Versions = "Err_Load_External_Versions";

        public const string Err_Load_Internal_Versions = "Err_Load_Internal_Versions";

        //public const string Err_Parse_Internal_Versions = "Err_Parse_Internal_Versions";

        public const string Err_Copy = "Err_Copy";


        //AsyncOperation _op;
        Versions _versionsExternal;

        float _lastProgress;

        //========== copy

        long _haveCopyBytes = 0;
        long _totalCopyBytes = 0;

        readonly List<FileVersion> _haveCopyFiles = new List<FileVersion>();
        readonly Queue<FileVersion> _toCopyFiles = new Queue<FileVersion>();
        readonly List<FileVersion> _copyFailFiles = new List<FileVersion>();

        readonly List<AsyncCopyInternalFile> _inCopyActionList = new List<AsyncCopyInternalFile>();
        readonly Queue<AsyncCopyInternalFile> _idleCopyActionQueue = new Queue<AsyncCopyInternalFile>();

        //==========
        

        public float CheckProgress()
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
                return 0f;

            case "LoadExternalVersions":
            case "LoadInternalVersions":
                return 0.1f;

            case "CopyFiles":
            {
                var inCopyBytes = 0f;
                for (var i = 0; i < _inCopyActionList.Count; ++i)
                {
                    inCopyBytes += _inCopyActionList[i].CheckCopyBytes();
                }

                var prg = 0.1f + 0.9f * (inCopyBytes + _haveCopyBytes) / _totalCopyBytes;
                if (_lastProgress > prg)
                    return _lastProgress;

                _lastProgress = prg;
                return prg;
            }

            }

            return 1f;
        }


        public void Extract()
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
            case BaseAsyncActionState.Finish:
                break;

            default:
                return;
            }

            _state = "LoadExternalVersions";
            var externalPath = ResPath.BuildExternalBundlePath("Versions.bin");
            if (File.Exists(externalPath))
            {
                try
                {
                    var ver2 = Versions.CreateFromFile(externalPath);
                    _versionsExternal = ver2;
                }
                catch (Exception ex) //文件读取失败
                {
                    Debug.LogError($"ExtractInternalFiles: msg: {ex.Message}");

                    SetError(Err_Load_External_Versions, $"external Versions file load fail");
                    SetFinishAndNotify(ErrMsg);
                    return;
                }
            }
            else
            {
                if (Directory.Exists(ResPath.ExternalBundleDir))
                {
                    var dirFiles = Directory.GetFiles(ResPath.ExternalBundleDir, "*.*", SearchOption.TopDirectoryOnly);
                    Debug.Log($"external Versions file not exist, check other files ct: {dirFiles.Length > 0}");
                }

                _versionsExternal = null;
            }

            _state = "LoadInternalVersions";
            var internalPath = ResPath.BuildInternalBundlePath(Versions.File_Name);

            var webReq = UnityWebRequest.Get(internalPath);
            var op = webReq.SendWebRequest();
            op.completed += OnComplete_LoadInternalVersions;
        }

        void OnComplete_LoadInternalVersions(AsyncOperation op)
        {
            if (false) Debug.Log($"ExtractInternalFiles: complete load internal Versions.bin");

            var request = (UnityWebRequestAsyncOperation)op;
            var webReq = request.webRequest;
            if (!string.IsNullOrEmpty(webReq.error))
            {
                SetError(Err_Load_Internal_Versions, $"internal Versions files load fail");
                Debug.LogError(ErrMsg);
                SetFinishAndNotify(ErrMsg);
                return;
            }

            Versions versionsInternal;
            try
            {
                versionsInternal = Versions.CreateFromString(request.webRequest.downloadHandler.text);
            }
            catch (Exception ex)
            {
                Debug.LogError($"msg: {ex.Message}, st: {ex.StackTrace}");

                SetError(Err_Load_Internal_Versions, $"internal Versions files parse fail");
                SetFinishAndNotify(ErrMsg);
                return;
            }

            if (null != _versionsExternal && versionsInternal.resVersion <= _versionsExternal.resVersion)
            {
                Debug.Log($"no need copy: resVer1: {versionsInternal.resVersion}, resVer2: {_versionsExternal.resVersion}");
                SetError("", "");
                SetFinishAndNotify("");
                return;
            }

            _haveCopyFiles.Clear();
            _toCopyFiles.Clear();
            _copyFailFiles.Clear();

            _totalCopyBytes = 0;
            _haveCopyBytes = 0;

            if (null == _versionsExternal) //完整copy
            {
                Debug.Log($"ExtractInternalFiles: full copy");

                foreach (var entry in versionsInternal.files)
                {
                    var fv = entry.Value;
                    _toCopyFiles.Enqueue(fv);
                    _totalCopyBytes += fv.len;
                }
            }
            else
            {
                Debug.Log($"ExtractInternalFiles: copy some");

                foreach (var entry in versionsInternal.files)
                {
                    var internalFv = entry.Value;

                    if (_versionsExternal.files.TryGetValue(entry.Key, out var externalFv)) //老资源
                    {
                        if (internalFv.crc != externalFv.crc) //资源变动了
                        {
                            _toCopyFiles.Enqueue(internalFv);
                            _totalCopyBytes += internalFv.len;
                        }
                        else
                        {
                            //资源没变动, 也要检查实际磁盘文件的情况, 而不是仅仅通过Versions配置文件
                            var filePath = ResPath.BuildExternalBundlePath(internalFv.path);
                            if (File.Exists(filePath)) //文件存在, 大小不一致就要重新copy
                            {
                                var fi = new FileInfo(filePath);
                                if (fi.Length != internalFv.len)
                                {
                                    _toCopyFiles.Enqueue(internalFv);
                                    _totalCopyBytes += internalFv.len;
                                }
                            }
                            else //文件不存在, 则直接重新copy
                            {
                                _toCopyFiles.Enqueue(internalFv);
                                _totalCopyBytes += internalFv.len;
                            }
                        }
                    }
                    else //新增资源
                    {
                        _toCopyFiles.Enqueue(internalFv);
                        _totalCopyBytes += internalFv.len;
                    }
                }
            }

            if (false) Debug.Log($"totalCopyFileNum: {_toCopyFiles.Count}, totalCopyBytes: {_totalCopyBytes}");
            _toCopyFiles.Enqueue(new FileVersion("Versions.bin", 1024));
            _totalCopyBytes += 1024;

            _lastProgress = 0;
            _state = "CopyFiles";
            CheckToCopyFile();
        }

        /// <summary>
        /// 继续把拷贝失败的重新拷贝
        /// </summary>
        public void ReCopyFailFiles()
        {
            if (BaseAsyncActionState.Finish != _state)
                return;

            if (_copyFailFiles.Count <= 0)
                return;

            for (var i = 0; i < _copyFailFiles.Count; ++i)
            {
                _toCopyFiles.Enqueue(_copyFailFiles[i]);
            }
            _copyFailFiles.Clear();

            _lastProgress = 0;
            _state = "CopyFiles";
            CheckToCopyFile();
        }

        //多个文件同时异步copy
        void CheckToCopyFile()
        {
            if (_toCopyFiles.Count <= 0 && _inCopyActionList.Count <= 0) //所有都copy完了
            {
                if (_copyFailFiles.Count > 0)
                {
                    SetError(Err_Copy, $"some files copy fail: {_copyFailFiles.Count}");
                    Debug.LogError(ErrMsg);
                    SetFinishAndNotify(ErrMsg);
                }
                else
                {
                    Debug.Log($"ExtractInternalFiles: all internal files copy");
                    SetError("", "");
                    SetFinishAndNotify("");
                }

                return;
            }

            var diff = Max_Concurrent_Copy_Num - _inCopyActionList.Count;
            for (var i = 0; i < diff && _toCopyFiles.Count > 0; ++i)
            {
                var fv = _toCopyFiles.Dequeue();

                AsyncCopyInternalFile act;
                if (_idleCopyActionQueue.Count > 0)
                    act = _idleCopyActionQueue.Dequeue();
                else
                    act = new AsyncCopyInternalFile();

                act.onComplete.AddListenerWithData(OnComplete_CopyOneFile, fv);
                act.Copy(fv.path);
                _inCopyActionList.Add(act);
            }
        }

        void OnComplete_CopyOneFile(object obj, string type, object userData)
        {
            var act = (AsyncCopyInternalFile)obj;
            
            if (_idleCopyActionQueue.Count < 5) //最多5个
                _idleCopyActionQueue.Enqueue(act);
            _inCopyActionList.Remove(act);

            var fv = (FileVersion)userData;

            if (false) Debug.Log($"OnComplete_CopyFile: inCopy: {_inCopyActionList.Count}, hasCopy: {_haveCopyFiles.Count}");

            if (!string.IsNullOrEmpty(act.ErrType))
            {
                _copyFailFiles.Add(fv);
            }
            else
            {
                _haveCopyFiles.Add(fv);
                _haveCopyBytes += fv.len;
            }

            CheckToCopyFile();
        }
        

    } //end of class


}
